package laboratoryQ.experiment.settings;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import laboratoryQ.BaseUtils;
import laboratoryQ.experiment.settings.SettingsAnnotations.Hidden;
import laboratoryQ.experiment.settings.SettingsAnnotations.Increase;

public class ReflectionBasedSettingsContainer implements ISettingsContainer {
	private static class SettingsItem implements ISettingsItem {
		private IExperimentSettings settings;
		private Field field;
		private Increase incrAnn = null;
		
		public SettingsItem(
				IExperimentSettings experimentSettings, 
				Field field
				) {
			this.settings = experimentSettings;
			this.field = field;
			incrAnn = field.getAnnotation(Increase.class);
		}
		
		public static boolean isHidden(Field field) {
			Hidden hidden = field.getAnnotation(Hidden.class);
			return hidden != null;
		}

		@Override
		public void increase() {
			chValue(incrAnn.value());
		}
		
		@Override
		public void decrease() {
			chValue(-incrAnn.value());
		}

		@Override
		public String getName() {
			return field.getName();
		}

		@Override
		public Object getValue() {
			return BaseUtils.safeGetFieldVal(field, settings);
		}

		@Override
		public void setValue(Object value) {
			try{
				unsefeSetValue(value);
			} catch (IllegalArgumentException e) {
			}
		}

		@Override
		public boolean isIncreasable() {
			return incrAnn != null;
		}

		private void unsefeSetValue(Object value) {
			Class<?> fieldType = field.getType();
			if (fieldType == int.class) {
				int val = Integer.parseInt(value.toString());
				if (incrAnn != null) {
					val = (int) Math.min(val, incrAnn.boundEnd());
					val = (int) Math.max(val, incrAnn.boundBegin());
				}
				BaseUtils.safeSetFieldVal(field, settings, val);
			}
			if (fieldType == double.class) {
				double val = Double.parseDouble(value.toString());
				if (incrAnn != null) {
					val = Math.min(val, incrAnn.boundEnd());
					val = Math.max(val, incrAnn.boundBegin());
				}
				BaseUtils.safeSetFieldVal(field, settings, val);
			} else
			if (fieldType == boolean.class) {
				boolean val = Boolean.parseBoolean(value.toString());
				BaseUtils.safeSetFieldVal(field, settings, val);
			}
		}
		
		private void chValue(double delta) {
			Object val = BaseUtils.safeGetFieldVal(field, settings);
			if (field.getType() == int.class) {
				int oldVal = (Integer) val;
				oldVal += delta;
				oldVal = (int) Math.min(oldVal, incrAnn.boundEnd());
				oldVal = (int) Math.max(oldVal, incrAnn.boundBegin());
				BaseUtils.safeSetFieldVal(field, settings, oldVal);
			} else 
			if (field.getType() == double.class) {
				double oldVal = (Double) val;
				oldVal += delta;
				oldVal = Math.min(oldVal, incrAnn.boundEnd());
				oldVal = Math.max(oldVal, incrAnn.boundBegin());
				BaseUtils.safeSetFieldVal(field, settings, oldVal);
			} else
			if (field.getType() == boolean.class) {
				boolean isTrue = delta > 0;
				BaseUtils.safeSetFieldVal(field, settings, isTrue);
			}
		}
	}
	
	private List<ISettingsItem> itemsList = new ArrayList<ISettingsItem>();
	private IExperimentSettings experimentSettings;

	public ReflectionBasedSettingsContainer(IExperimentSettings experimentSettings) {
		this.experimentSettings = experimentSettings;
		for (Field field : BaseUtils.listFields(experimentSettings.getClass())) {
			if (!SettingsItem.isHidden(field)) {
				SettingsItem item = new SettingsItem(experimentSettings, field);
				itemsList.add(item);
			}
		}
	}
	
	public void sortFields(String ... orderedNames) {
		for (int i = 0; i < orderedNames.length; i++) {
			int pos = find(orderedNames[i]);
			if (pos == -1) {
				throw new RuntimeException("Name " + orderedNames[i] + " is invalid.");
			}
			swap(i, pos);
		}
	}

	private void swap(int a, int b) {
		ISettingsItem t = itemsList.get(a);
		itemsList.set(a, itemsList.get(b));
		itemsList.set(b, t);
	}

	private int find(String name) {
		for (int i = 0; i < itemsList.size(); i++) {
			if (name.equals(itemsList.get(i).getName())) {
				return i;
			}
		}
		return -1;
	}

	@Override
	public List<ISettingsItem> listItems() {
		return itemsList;
	}

	@Override
	public IExperimentSettings getSettings() {
		return experimentSettings;
	}
}
